﻿namespace InkType.Util

open System
open System.Collections.Generic
open System.Windows
open System.Windows.Data
open System.Windows.Ink
open System.Windows.Input
open System.Windows.Media


type IUndoRedo =
    abstract Undo : unit -> unit
    abstract Redo : unit -> unit

type ReentrancyMonitor () =
    let mutable count = 0

    member x.Enter () =
        count <- count + 1
        x :> IDisposable

    member x.IsBusy =
        count > 0

    interface IDisposable with
        member x.Dispose () =
            if count = 0 then
                invalidOp "ReentrancyMonitor: Exits > Entrances"
            count <- count - 1


type UndoRedoStack () =
    let undoStack = ref []
    let redoStack = ref []
    let reentrancyMonitor = new ReentrancyMonitor ()

    member private x.Enqueue a =
        if not reentrancyMonitor.IsBusy then
            undoStack := a :: !undoStack
            redoStack := []

    member x.EnqueueStrokesChanged (strokes : StrokeCollection) added removed =
        let added = new StrokeCollection (added : seq<_>)
        let removed = new StrokeCollection (removed : seq<_>)
        { new IUndoRedo with
            member x.Undo () =
                strokes.Remove added
                strokes.Add removed
            member x.Redo () =
                strokes.Remove removed
                strokes.Add added }
        |> x.Enqueue

    member x.EnqueueStrokesTransformed (strokes : StrokeCollection) newRect oldRect (selection : StrokeCollection) =
        let transformRectToRect (source : Rect) (dest : Rect) =
            let m = Matrix.Identity
            m.Translate (-source.X, -source.Y)
            m.Scale (dest.Width / source.Width, dest.Height / source.Height)
            m.Translate (dest.X, dest.Y)
            m
        { new IUndoRedo with
            member x.Undo () =
                selection.Transform (transformRectToRect newRect oldRect, false)
            member x.Redo () =
                selection.Transform (transformRectToRect oldRect newRect, false) }
        |> x.Enqueue

    member x.CanUndo () =
        not (List.isEmpty !undoStack)

    member x.CanRedo () =
        not (List.isEmpty !redoStack)

    member x.Undo () =
        match !undoStack with
        | [] -> invalidOp "No undo actions"
        | a :: rest ->
            use __ = reentrancyMonitor.Enter ()
            a.Undo ()
            undoStack := rest
            redoStack := a :: !redoStack

    member x.Redo () =
        match !redoStack with
        | [] -> invalidOp "No redo actions"
        | a :: rest ->
            use __ = reentrancyMonitor.Enter ()
            a.Redo ()
            redoStack := rest
            undoStack := a :: !undoStack


